/*
 * JSOOUtils.java
 *
 */

package ua.gradsoft.misc.jsoo;


import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import ua.gradsoft.javachecker.EntityNotFoundException;
import ua.gradsoft.javachecker.NotSupportedException;
import ua.gradsoft.javachecker.models.JavaAnnotationInstanceModel;
import ua.gradsoft.javachecker.models.JavaExpressionModel;
import ua.gradsoft.javachecker.models.JavaMethodModel;
import ua.gradsoft.javachecker.models.JavaPackageModel;
import ua.gradsoft.javachecker.models.JavaResolver;
import ua.gradsoft.javachecker.models.JavaTypeModel;
import ua.gradsoft.javachecker.models.JavaTypeVariableAbstractModel;
import ua.gradsoft.javachecker.models.MethodMatchingConversions;
import ua.gradsoft.javachecker.models.expressions.JavaObjectConstantExpressionModel;
import ua.gradsoft.javachecker.util.Pair;
import ua.gradsoft.termware.Term;
import ua.gradsoft.termware.TermWare;
import ua.gradsoft.termware.TermWareException;

/**
 *Static class for utilities
 * @author rssh
 */
public class JSOOUtils {
    
    
    public static Term createPackageTerm(JavaTypeModel tpm) throws TermWareException
    {
        JavaPackageModel jp = tpm.getPackageModel();
        String nameWithDots = jp.getName();
        String[] nameComponents = nameWithDots.split("\\.");
        Term[] tncs = new Term[nameComponents.length];
        for(int i=0; i<nameComponents.length; ++i) {
            Term st = TermWare.getInstance().getTermFactory().createString(nameComponents[i]);
            tncs[i] = TermWare.getInstance().getTermFactory().createTerm("Identifier",st);
        }
        Term nl = TermWare.getInstance().getTermFactory().createList(tncs);
        Term nt = TermWare.getInstance().getTermFactory().createTerm("Name",nl);
        Term retval = TermWare.getInstance().getTermFactory().createTerm("PackageDeclaration",nt);
        return retval;
    }

    //Nullable
    public static JavaMethodModel
               findBestAnnotatedApplicableMethod(String operator,
                                               JavaTypeModel frsType,
                                               List<JavaTypeModel> paramTypes
                                             ) throws TermWareException, JSOOProcessingException
    {
        boolean debug = false;
        if (debug) {
            System.err.println("search for annotated applicable method for ("+frsType.getName()+",...), operation "+operator);
        }

        List<Pair<JavaMethodModel,MethodMatchingConversions>> candidates = new ArrayList<Pair<JavaMethodModel, MethodMatchingConversions>>();
        try {
          findAnnotatedApplicableMethods(operator, frsType, paramTypes, candidates);
        }catch(EntityNotFoundException ex){
            throw new JSOOProcessingException("entity not found",ex);
        }
        JavaMethodModel retval = null;
        if (candidates.size()==0) {
            // not found.
            retval = null;
        }else if (candidates.size()==1) {
            retval = candidates.get(0).getFirst();
        }else if (candidates.size() > 0) {
            List<Pair<JavaMethodModel,MethodMatchingConversions>> bests =MethodMatchingConversionsHelper.bests(candidates);
            if (bests==null || bests.size()==0) {
                // impossible ?
                throw new JSOOProcessingException("Can't find best from candidates");
            }else if (bests.size()==1) {
                retval = bests.get(0).getFirst();
            }else{
                // for now we need only names, so let's search only different names.
                String name=null;
                String typeName = null;
                for(Pair<JavaMethodModel,MethodMatchingConversions> p: bests) {
                    if (name==null) {
                        name = p.getFirst().getName();
                        typeName = p.getFirst().getTypeModel().getFullName();
                    } else {
                        if (!name.equals(p.getFirst().getName())) {
                            String message = " can't choose between "+typeName+"."+name
                                    +" and "+p.getFirst().getTypeModel().getFullName()+"."
                                            +p.getFirst().getName()+" for operator "+operator;
                            throw new JSOOProcessingException(message);
                        }
                    }
                }
                // if we here, than all names are equals, so left choice probnlem to compiler.
                retval = bests.get(0).getFirst();
            }
        }
        return retval;
    }


    public static void
               findAnnotatedApplicableMethods(String operator,
                                               JavaTypeModel frsType,
                                               List<JavaTypeModel> paramTypes,
                                               List<Pair<JavaMethodModel,MethodMatchingConversions>> models
                                             )  throws TermWareException, EntityNotFoundException
    {
        boolean debug = false;
        if (debug) {
            System.err.println("seatch for annotated applicable methods for ("+frsType.getName()+",... ), operation "+operator);
        }
        // at first, search in currenc type.
        boolean found=false;
        Map<String,List<JavaMethodModel>> methodMap = null;
        try {
            methodMap = frsType.getMethodModels();
        }catch(NotSupportedException ex){
            methodMap = Collections.<String,List<JavaMethodModel>>emptyMap();
        }
        for(Map.Entry<String,List<JavaMethodModel>> e: methodMap.entrySet()) {
            List<JavaMethodModel> mms = e.getValue();
            for(JavaMethodModel m: mms) {
                if (debug) {
                    System.err.print("check "+m.getName()+":");
                }
                if (isAnnotatedByOperator(m,operator)) {
                   MethodMatchingConversions conversions = new MethodMatchingConversions();
                   if (JavaResolver.match(m.getFormalParametersList(), paramTypes,
                                           conversions, false, false)
                            ) {
                      Pair<JavaMethodModel, MethodMatchingConversions> pair = new Pair<JavaMethodModel, MethodMatchingConversions>(m,conversions);
                      models.add(pair);
                      found=true;
                      if (debug) {
                          System.err.println("matched");
                      }
                   }else{
                      if (debug) {
                        System.err.println("not matched");
                      }
                   }
                }else{
                  if (debug) {
                    System.err.println("not annotated");
                  }
                }
            }
        }

        
        if (!found) {
            // search in superclasses and interfaces.
            List<JavaTypeModel> modelsToCheck = new ArrayList<JavaTypeModel>();
            
            JavaTypeModel frsSuper = null;
            try {
                frsSuper = frsType.getSuperClass();
            }catch(NotSupportedException ex){
                // normal, we does not need one,
                ;
            }                
            boolean skip=false;
            if (frsType.isEnum()) {
               if (frsSuper.getErasedFullName().equals("java.lang.Enum"))  {
                   skip=true;
               }
            }else if (frsType.isTypeVariable()) {
                JavaTypeVariableAbstractModel tv = (JavaTypeVariableAbstractModel)frsType;
                List<JavaTypeModel> bounds = tv.getBounds();
                modelsToCheck = bounds;                
            }else if (!frsType.getErasedFullName().equals("java.lang.Object")) {
                if (frsSuper!=null) {
                    modelsToCheck.add(frsSuper);
                }
            }
          
            try {
              List<JavaTypeModel> interfaces = frsType.getSuperInterfaces();
              if (!interfaces.isEmpty()) {
                modelsToCheck.addAll(interfaces);
              }
            }catch(NotSupportedException ex){
                ;
            }
            
            
            if (!modelsToCheck.isEmpty()) {
                List<Pair<JavaMethodModel,MethodMatchingConversions>> superSearch = 
                        new ArrayList<Pair<JavaMethodModel,MethodMatchingConversions>>();
                for(JavaTypeModel tm: modelsToCheck) {
                    findAnnotatedApplicableMethods(operator,tm,paramTypes,superSearch);
                }
                for(Pair<JavaMethodModel,MethodMatchingConversions> p: superSearch) {
                //    p.getSecond().incrementNSupers();
                    models.add(p);
                }                
                
            }
                                
        }



    }


    public static boolean isAnnotatedByOperator(JavaMethodModel m,String operator) throws TermWareException, EntityNotFoundException
    {
                Map<String,JavaAnnotationInstanceModel> ants = m.getAnnotationsMap();
                JavaAnnotationInstanceModel ant = ants.get("java.lang.Operator");
                if (ant!=null) {
                   try {
                     JavaExpressionModel ope = ant.getElement("value");
                     if (ope instanceof JavaObjectConstantExpressionModel) {
                       JavaObjectConstantExpressionModel cem = (JavaObjectConstantExpressionModel)ope;
                       Object o = cem.getConstant();
                       if (o instanceof String) {
                           // ok, now check that this is same as operator
                           return (operator.equals(o));
                       }

                     }
                   }catch(NotSupportedException ex){
                       /* ignore */ ;
                   }
                }
                return false;

    }


    public static Term createIdentifier(String s) throws TermWareException
    {
       return TermWare.getInstance().getTermFactory().createTerm("Identifier",
               TermWare.getInstance().getTermFactory().createString(s)
               );
    }

    public static Term createStringTerm(String s)
    {
        return TermWare.getInstance().getTermFactory().createString(s);
    }
    
}
